/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.kafka.clients;

import org.apache.kafka.common.config.AbstractConfig;
import org.apache.kafka.common.config.ConfigException;
import org.apache.kafka.common.config.SaslConfigs;
import org.apache.kafka.common.metrics.JmxReporter;
import org.apache.kafka.common.metrics.MetricsReporter;
import org.apache.kafka.common.security.auth.SecurityProtocol;
import org.apache.kafka.common.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Configurations shared by Kafka client applications: producer, consumer, connect, etc.
 */
// Kafka 客户端应用程序共享的配置：producer、consumer、connect 等。
public class CommonClientConfigs {
    private static final Logger log = LoggerFactory.getLogger(CommonClientConfigs.class);

    // ----------------------------------------------------------------
    /*
     * NOTE: DO NOT CHANGE EITHER CONFIG NAMES AS THESE ARE PART OF THE PUBLIC API AND CHANGE WILL BREAK USER CODE.
     */

    public static final String BOOTSTRAP_SERVERS_CONFIG = "bootstrap.servers";

    /**
     * A list of host/port pairs to use for establishing the initial connection to the Kafka cluster.
     * The client will make use of all servers irrespective of which servers are specified here for bootstrapping&mdash;
     * this list only impacts the initial hosts used to discover the full set of servers.
     * This list should be in the form <code>host1:port1,host2:port2,...</code>.
     * Since these servers are just used for the initial connection to discover the full cluster membership (which may change dynamically),
     * this list need not contain the full set of servers (you may want more than one, though, in case a server is down).
     *
     * 用于指定要连接的Kafka集群的ip:端口列表
     * 在配置Kafka集群时，bootstrap.servers参数是一个关键配置项，它指定了用于与Kafka集群建立初始连接的服务器列表。
     * 这个列表中的服务器将被客户端使用，无论它们是如何指定的，以进行初始连接，从而发现集群中的所有服务器。
     * 这个列表仅影响用于发现完整集群成员资格的初始主机集合，而这些服务器在动态变化的情况下可能并不包含集群中的全部服务器。
     * 因此，为了确保客户端能够连接到集群中的所有服务器，通常建议提供多个服务器地址，以防某个服务器不可用时，客户端仍然可以通过其他服务器进行连接‌
     *
     */
    // 用于建立与 Kafka 集群的初始连接的主机/端口对列表。
    // 客户端将使用所有服务器，而不管此处指定了哪些服务器进行引导
    // — 此列表仅影响用于发现完整服务器集的初始主机。
    // 此列表应采用 host1:port1,host2:port2,....
    // 由于这些服务器仅用于初始连接以发现完整的集群成员身份（可能会动态更改），
    // 因此此列表不需要包含完整的服务器集（尽管您可能需要多个服务器，以防服务器关闭）。
    public static final String BOOTSTRAP_SERVERS_DOC = "A list of host/port pairs to use for establishing the initial connection to the Kafka cluster. The client will make use of all servers irrespective of which servers are specified here for bootstrapping&mdash;this list only impacts the initial hosts used to discover the full set of servers. This list should be in the form "
                                                       + "<code>host1:port1,host2:port2,...</code>. Since these servers are just used for the initial connection to "
                                                       + "discover the full cluster membership (which may change dynamically), this list need not contain the full set of "
                                                       + "servers (you may want more than one, though, in case a server is down).";

    // ----------------------------------------------------------------

    public static final String CLIENT_DNS_LOOKUP_CONFIG = "client.dns.lookup";
    /**
     * Controls how the client uses DNS lookups. If set to <code>use_all_dns_ips</code>, connect to each returned IP address in sequence until a successful connection is established. After a disconnection, the next IP is used. Once all IPs have been used once, the client resolves the IP(s) from the hostname again (both the JVM and the OS cache DNS name lookups, however). If set to <code>resolve_canonical_bootstrap_servers_only</code>, resolve each bootstrap address into a list of canonical names. After the bootstrap phase, this behaves the same as <code>use_all_dns_ips</code>.
     */
    // 控制客户端如何使用 DNS 查找。
    // 如果设置为 use_all_dns_ips，则按顺序连接到每个返回的 IP 地址，直到成功建立连接。
    // 断开连接后，将使用下一个 IP。
    // 一旦所有 IP 都被使用了一次，客户端就会再次从主机名解析 IP（但是，JVM 和 OS 缓存 DNS 名称查找）。
    // 如果设置为 resolve_canonical_bootstrap_servers_only，则将每个引导地址解析为规范名称列表。
    // 在 bootstrap 阶段之后，其行为与 use_all_dns_ips相同。
    public static final String CLIENT_DNS_LOOKUP_DOC = "Controls how the client uses DNS lookups. "
                                                       + "If set to <code>use_all_dns_ips</code>, connect to each returned IP "
                                                       + "address in sequence until a successful connection is established. "
                                                       + "After a disconnection, the next IP is used. Once all IPs have been "
                                                       + "used once, the client resolves the IP(s) from the hostname again "
                                                       + "(both the JVM and the OS cache DNS name lookups, however). "
                                                       + "If set to <code>resolve_canonical_bootstrap_servers_only</code>, "
                                                       + "resolve each bootstrap address into a list of canonical names. After "
                                                       + "the bootstrap phase, this behaves the same as <code>use_all_dns_ips</code>.";

    // ----------------------------------------------------------------

    public static final String METADATA_MAX_AGE_CONFIG = "metadata.max.age.ms";

    /**
     * The period of time in milliseconds after which we force a refresh of metadata even if we haven't seen any partition
     * leadership changes to proactively discover any new brokers or partitions.
     */
    // 以毫秒为单位的时间段，在此之后，即使我们没有看到任何分区Leader更改以主动发现任何新的代理或分区，我们也会强制刷新元数据。
    public static final String METADATA_MAX_AGE_DOC = "The period of time in milliseconds after which we force a refresh of metadata even if we haven't seen any partition leadership changes to proactively discover any new brokers or partitions.";

    // ----------------------------------------------------------------

    public static final String SEND_BUFFER_CONFIG = "send.buffer.bytes";

    /**
     * The size of the TCP send buffer (SO_SNDBUF) to use when sending data. If the value is -1, the OS default will be used.
     */
    // TCP发送数据才缓存区大小，如果值为-1，则使用操作系统的默认值
    public static final String SEND_BUFFER_DOC = "The size of the TCP send buffer (SO_SNDBUF) to use when sending data. If the value is -1, the OS default will be used.";
    public static final int SEND_BUFFER_LOWER_BOUND = -1;

    // ----------------------------------------------------------------

    public static final String RECEIVE_BUFFER_CONFIG = "receive.buffer.bytes";

    /**
     * The size of the TCP receive buffer (SO_RCVBUF) to use when reading data. If the value is -1, the OS default will be used.
     */
    // TCP接收数据才缓存区大小，如果值为-1，则使用操作系统的默认值
    public static final String RECEIVE_BUFFER_DOC = "The size of the TCP receive buffer (SO_RCVBUF) to use when reading data. If the value is -1, the OS default will be used.";
    public static final int RECEIVE_BUFFER_LOWER_BOUND = -1;

    // ----------------------------------------------------------------

    public static final String CLIENT_ID_CONFIG = "client.id";

    /**
     * An id string to pass to the server when making requests.
     * The purpose of this is to be able to track the source of requests beyond just ip/port by allowing a logical application name to be included in server-side request logging.
     */
    /*
     发起请求时传递给服务器的标识字符串。
     通过允许在服务端请求日志中包含逻辑应用名称，
     此参数使得请求来源的追踪不仅限于IP/端口信息。
     */
    public static final String CLIENT_ID_DOC = "An id string to pass to the server when making requests. The purpose of this is to be able to track the source of requests beyond just ip/port by allowing a logical application name to be included in server-side request logging.";

    // ----------------------------------------------------------------

    public static final String CLIENT_RACK_CONFIG = "client.rack";

    /**
     * A rack identifier for this client. This can be any string value which indicates where this client is physically located.
     * It corresponds with the broker config 'broker.rack'
     */
    /*
     客户端的机架标识符。该字段可以是任意字符串值，用于指明客户端的物理位置。
     需与Broker的配置参数'broker.rack'保持一致。
     */
    public static final String CLIENT_RACK_DOC = "A rack identifier for this client. This can be any string value which indicates where this client is physically located. It corresponds with the broker config 'broker.rack'";

    // ----------------------------------------------------------------

    public static final String RECONNECT_BACKOFF_MS_CONFIG = "reconnect.backoff.ms";

    /**
     * The base amount of time to wait before attempting to reconnect to a given host.
     * This avoids repeatedly connecting to a host in a tight loop.
     * This backoff applies to all connection attempts by the client to a broker.
     */
    /*
     在尝试重新连接到指定主机前的基础等待时间。
     该机制可避免在紧凑循环中反复尝试连接同一主机。
     此退避策略适用于客户端与Broker之间的所有连接尝试。
     */
    public static final String RECONNECT_BACKOFF_MS_DOC = "The base amount of time to wait before attempting to reconnect to a given host. This avoids repeatedly connecting to a host in a tight loop. This backoff applies to all connection attempts by the client to a broker.";

    // ----------------------------------------------------------------

    public static final String RECONNECT_BACKOFF_MAX_MS_CONFIG = "reconnect.backoff.max.ms";

    /**
     * The maximum amount of time in milliseconds to wait when reconnecting to a broker that has repeatedly failed to connect.
     * If provided, the backoff per host will increase exponentially for each consecutive connection failure, up to this maximum.
     * After calculating the backoff increase, 20% random jitter is added to avoid connection storms.
     */
    /*
     重连持续失败的Broker时的最大等待时间（毫秒）。
     若设置此值，针对每个主机的退避时间将随连续失败次数呈指数增长，直至达到此上限值。
     计算退避增量后，会添加20%的随机抖动，以避免连接风暴。
     */
    public static final String RECONNECT_BACKOFF_MAX_MS_DOC = "The maximum amount of time in milliseconds to wait when reconnecting to a broker that has repeatedly failed to connect. If provided, the backoff per host will increase exponentially for each consecutive connection failure, up to this maximum. After calculating the backoff increase, 20% random jitter is added to avoid connection storms.";

    // ----------------------------------------------------------------

    public static final String RETRIES_CONFIG = "retries";

    /**
     * Setting a value greater than zero will cause the client to resend any request that fails with a potentially transient error.
     * It is recommended to set the value to either zero or `MAX_VALUE` and use corresponding timeout parameters to control how long a client should retry a request.
     */
    /*
     当设置大于零的值时，客户端会重试所有因可能暂时性错误而失败的请求。
     建议将该值设置为零或MAX_VALUE，并通过相应的超时参数来控制客户端的请求重试时长。
     */
    public static final String RETRIES_DOC = "Setting a value greater than zero will cause the client to resend any request that fails with a potentially transient error." +
        " It is recommended to set the value to either zero or `MAX_VALUE` and use corresponding timeout parameters to control how long a client should retry a request.";

    // ----------------------------------------------------------------

    public static final String RETRY_BACKOFF_MS_CONFIG = "retry.backoff.ms";

    /**
     * The amount of time to wait before attempting to retry a failed request to a given topic partition.
     * This avoids repeatedly sending requests in a tight loop under some failure scenarios.
     */
    /*
     重试对指定主题分区（topic partition）失败请求前的等待时间。
     该机制可避免在某些故障场景下频繁循环发送请求。
     */
    public static final String RETRY_BACKOFF_MS_DOC = "The amount of time to wait before attempting to retry a failed request to a given topic partition. This avoids repeatedly sending requests in a tight loop under some failure scenarios.";

    // ----------------------------------------------------------------

    public static final String METRICS_SAMPLE_WINDOW_MS_CONFIG = "metrics.sample.window.ms";

    /**
     * The window of time a metrics sample is computed over.
     */
    // 指标采样计算的时间窗口范围。
    public static final String METRICS_SAMPLE_WINDOW_MS_DOC = "The window of time a metrics sample is computed over.";

    // ----------------------------------------------------------------

    public static final String METRICS_NUM_SAMPLES_CONFIG = "metrics.num.samples";

    /**
     * The number of samples maintained to compute metrics.
     */
    // 用于计算指标的采样数。
    public static final String METRICS_NUM_SAMPLES_DOC = "The number of samples maintained to compute metrics.";

    // ----------------------------------------------------------------

    public static final String METRICS_RECORDING_LEVEL_CONFIG = "metrics.recording.level";

    /**
     * The highest recording level for metrics.
     */
    // 指标的最高记录级别。
    public static final String METRICS_RECORDING_LEVEL_DOC = "The highest recording level for metrics.";

    // ----------------------------------------------------------------

    public static final String METRIC_REPORTER_CLASSES_CONFIG = "metric.reporters";

    /**
     * A list of classes to use as metrics reporters.
     * Implementing the <code>org.apache.kafka.common.metrics.MetricsReporter</code> interface allows plugging in classes that will be notified of new metric creation.
     * The JmxReporter is always included to register JMX statistics.
     */
    /*
     用作指标报告器的类列表。
     实现<code>org.apache.kafka.common.metrics.MetricsReporter</code>接口的类可接收新指标创建的通知。
     JmxReporter默认包含在内，用于注册JMX统计信息。
     */
    public static final String METRIC_REPORTER_CLASSES_DOC = "A list of classes to use as metrics reporters. Implementing the <code>org.apache.kafka.common.metrics.MetricsReporter</code> interface allows plugging in classes that will be notified of new metric creation. The JmxReporter is always included to register JMX statistics.";

    // ----------------------------------------------------------------

    public static final String METRICS_CONTEXT_PREFIX = "metrics.context.";

    // ----------------------------------------------------------------

    @Deprecated
    public static final String AUTO_INCLUDE_JMX_REPORTER_CONFIG = "auto.include.jmx.reporter";

    /**
     * Deprecated.
     * Whether to automatically include JmxReporter even if it's not listed in <code>metric.reporters</code>.
     * This configuration will be removed in Kafka 4.0, users should instead include <code>org.apache.kafka.common.metrics.JmxReporter</code> in <code>metric.reporters</code> in order to enable the JmxReporter.
     */
    /*
     【已弃用】
     是否自动包含JmxReporter，即使未在<code>metric.reporters</code>中列出。
     该配置将在Kafka 4.0中移除，用户应改为在<code>metric.reporters</code>中
     添加<code>org.apache.kafka.common.metrics.JmxReporter</code>来启用JmxReporter。
     */
    public static final String AUTO_INCLUDE_JMX_REPORTER_DOC = "Deprecated. Whether to automatically include JmxReporter even if it's not listed in <code>metric.reporters</code>. This configuration will be removed in Kafka 4.0, users should instead include <code>org.apache.kafka.common.metrics.JmxReporter</code> in <code>metric.reporters</code> in order to enable the JmxReporter.";

    // ----------------------------------------------------------------

    public static final String SECURITY_PROTOCOL_CONFIG = "security.protocol";

    /**
     * Protocol used to communicate with brokers. Valid values are: PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL.
     */
    /*
     与Broker通信使用的协议。可选值包括：PLAINTEXT、SSL、SASL_PLAINTEXT、SASL_SSL。
     */
    public static final String SECURITY_PROTOCOL_DOC = "Protocol used to communicate with brokers. Valid values are: " +
        Utils.join(SecurityProtocol.names(), ", ") + ".";
    public static final String DEFAULT_SECURITY_PROTOCOL = "PLAINTEXT";

    // ----------------------------------------------------------------

    public static final String SOCKET_CONNECTION_SETUP_TIMEOUT_MS_CONFIG = "socket.connection.setup.timeout.ms";

    /**
     * The amount of time the client will wait for the socket connection to be established.
     * If the connection is not built before the timeout elapses, clients will close the socket channel.
     */
    /*
     客户端等待建立socket连接的超时时间。
     若在超时前未能成功建立连接，客户端将关闭socket通道。
     */
    public static final String SOCKET_CONNECTION_SETUP_TIMEOUT_MS_DOC = "The amount of time the client will wait for the socket connection to be established. If the connection is not built before the timeout elapses, clients will close the socket channel.";
    public static final Long DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT_MS = 10 * 1000L;

    // ----------------------------------------------------------------

    public static final String SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_CONFIG = "socket.connection.setup.timeout.max.ms";

    /**
     * The maximum amount of time the client will wait for the socket connection to be established.
     * The connection setup timeout will increase exponentially for each consecutive connection failure up to this maximum.
     * To avoid connection storms, a randomization factor of 0.2 will be applied to the timeout resulting in a random range between 20% below and 20% above the computed value.
     */
    /*
     客户端等待建立socket连接的最长超时时间。
     连接建立超时时间会随连续失败次数呈指数增长，直至达到此上限值。
     为避免连接风暴，将应用0.2的随机因子，使实际超时时间在计算值的±20%范围内波动。
     */
    public static final String SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS_DOC = "The maximum amount of time the client will wait for the socket connection to be established. The connection setup timeout will increase exponentially for each consecutive connection failure up to this maximum. To avoid connection storms, a randomization factor of 0.2 will be applied to the timeout resulting in a random range between 20% below and 20% above the computed value.";
    public static final Long DEFAULT_SOCKET_CONNECTION_SETUP_TIMEOUT_MAX_MS = 30 * 1000L;

    // ----------------------------------------------------------------

    public static final String CONNECTIONS_MAX_IDLE_MS_CONFIG = "connections.max.idle.ms";

    /**
     * Close idle connections after the number of milliseconds specified by this config.
     */
    // 当连接空闲时间超过此配置指定的毫秒数后，将关闭该连接。
    public static final String CONNECTIONS_MAX_IDLE_MS_DOC = "Close idle connections after the number of milliseconds specified by this config.";

    // ----------------------------------------------------------------

    public static final String REQUEST_TIMEOUT_MS_CONFIG = "request.timeout.ms";

    /**
     * The configuration controls the maximum amount of time the client will wait for the response of a request.
     * If the response is not received before the timeout elapses the client will resend the request if necessary or fail the request if retries are exhausted.
     */
    /*
     此配置控制客户端等待请求响应的最长时间。
     如果在超时到期前未收到响应，客户端将在必要时重发请求，
     若重试次数已用尽则判定该请求失败。
     */
    public static final String REQUEST_TIMEOUT_MS_DOC = "The configuration controls the maximum amount of time the client will wait "
                                                         + "for the response of a request. If the response is not received before the timeout "
                                                         + "elapses the client will resend the request if necessary or fail the request if "
                                                         + "retries are exhausted.";

    // ----------------------------------------------------------------

    public static final String DEFAULT_LIST_KEY_SERDE_INNER_CLASS = "default.list.key.serde.inner";

    /**
     * Default inner class of list serde for key that implements the <code>org.apache.kafka.common.serialization.Serde</code> interface.
     * This configuration will be read if and only if <code>default.key.serde</code> configuration is set to <code>org.apache.kafka.common.serialization.Serdes.ListSerde</code>
     */
    /*
     键(key)的默认列表序列化/反序列化类(实现<code>org.apache.kafka.common.serialization.Serde</code>接口的内置类)。
     仅当<code>default.key.serde</code>配置设为<code>org.apache.kafka.common.serialization.Serdes.ListSerde</code>时，
     才会读取此配置项。
     */
    public static final String DEFAULT_LIST_KEY_SERDE_INNER_CLASS_DOC = "Default inner class of list serde for key that implements the <code>org.apache.kafka.common.serialization.Serde</code> interface. "
            + "This configuration will be read if and only if <code>default.key.serde</code> configuration is set to <code>org.apache.kafka.common.serialization.Serdes.ListSerde</code>";

    // ----------------------------------------------------------------

    public static final String DEFAULT_LIST_VALUE_SERDE_INNER_CLASS = "default.list.value.serde.inner";

    /**
     * Default inner class of list serde for value that implements the <code>org.apache.kafka.common.serialization.Serde</code> interface.
     * This configuration will be read if and only if <code>default.value.serde</code> configuration is set to <code>org.apache.kafka.common.serialization.Serdes.ListSerde</code>
     */
    /*
     值(value)的默认列表序列化/反序列化类(实现<code>org.apache.kafka.common.serialization.Serde</code>接口的内置类)。
     仅当<code>default.value.serde</code>配置设为<code>org.apache.kafka.common.serialization.Serdes.ListSerde</code>时，
     才会读取此配置项。
     */
    public static final String DEFAULT_LIST_VALUE_SERDE_INNER_CLASS_DOC = "Default inner class of list serde for value that implements the <code>org.apache.kafka.common.serialization.Serde</code> interface. "
            + "This configuration will be read if and only if <code>default.value.serde</code> configuration is set to <code>org.apache.kafka.common.serialization.Serdes.ListSerde</code>";

    // ----------------------------------------------------------------

    public static final String DEFAULT_LIST_KEY_SERDE_TYPE_CLASS = "default.list.key.serde.type";

    /**
     * Default class for key that implements the <code>java.util.List</code> interface.
     * This configuration will be read if and only if <code>default.key.serde</code> configuration is set to <code>org.apache.kafka.common.serialization.Serdes.ListSerde</code> Note when list serde class is used,
     * one needs to set the inner serde class that implements the <code>org.apache.kafka.common.serialization.Serde</code> interface via 'default.list.key.serde.inner'
     */
    /*
     键(key)的默认List实现类（实现<code>java.util.List</code>接口）。
     仅当<code>default.key.serde</code>配置设为<code>org.apache.kafka.common.serialization.Serdes.ListSerde</code>时才会读取此配置。
     注意：使用List序列化类时，需要通过'default.list.key.serde.inner'配置项
     指定实现<code>org.apache.kafka.common.serialization.Serde</code>接口的内部序列化类。
     */
    public static final String DEFAULT_LIST_KEY_SERDE_TYPE_CLASS_DOC = "Default class for key that implements the <code>java.util.List</code> interface. "
            + "This configuration will be read if and only if <code>default.key.serde</code> configuration is set to <code>org.apache.kafka.common.serialization.Serdes.ListSerde</code> "
            + "Note when list serde class is used, one needs to set the inner serde class that implements the <code>org.apache.kafka.common.serialization.Serde</code> interface via '"
            + DEFAULT_LIST_KEY_SERDE_INNER_CLASS + "'";

    // ----------------------------------------------------------------

    public static final String DEFAULT_LIST_VALUE_SERDE_TYPE_CLASS = "default.list.value.serde.type";

    /**
     * Default class for value that implements the <code>java.util.List</code> interface.
     * This configuration will be read if and only if <code>default.value.serde</code> configuration is set to <code>org.apache.kafka.common.serialization.Serdes.ListSerde</code>
     * Note when list serde class is used, one needs to set the inner serde class that implements the <code>org.apache.kafka.common.serialization.Serde</code> interface via 'default.list.value.serde.inner'
     */
    /*
     值(value)的默认List实现类（实现<code>java.util.List</code>接口）。
     仅当<code>default.value.serde</code>配置设为<code>org.apache.kafka.common.serialization.Serdes.ListSerde</code>时才会读取此配置。
     注意：使用List序列化类时，需要通过'default.list.value.serde.inner'配置项
     指定实现<code>org.apache.kafka.common.serialization.Serde</code>接口的内部序列化类。
     */
    public static final String DEFAULT_LIST_VALUE_SERDE_TYPE_CLASS_DOC = "Default class for value that implements the <code>java.util.List</code> interface. "
            + "This configuration will be read if and only if <code>default.value.serde</code> configuration is set to <code>org.apache.kafka.common.serialization.Serdes.ListSerde</code> "
            + "Note when list serde class is used, one needs to set the inner serde class that implements the <code>org.apache.kafka.common.serialization.Serde</code> interface via '"
            + DEFAULT_LIST_VALUE_SERDE_INNER_CLASS + "'";

    // ----------------------------------------------------------------

    public static final String GROUP_ID_CONFIG = "group.id";

    /**
     * A unique string that identifies the consumer group this consumer belongs to.
     * This property is required if the consumer uses either the group management functionality by using <code>subscribe(topic)</code> or the Kafka-based offset management strategy.
     */
    /*
     标识该消费者所属消费者组的唯一字符串。
     如果消费者通过<code>subscribe(topic)</code>方式使用组管理功能，
     或采用基于Kafka的偏移量管理策略，则必须配置此属性。
     */
    public static final String GROUP_ID_DOC = "A unique string that identifies the consumer group this consumer belongs to. This property is required if the consumer uses either the group management functionality by using <code>subscribe(topic)</code> or the Kafka-based offset management strategy.";

    // ----------------------------------------------------------------

    public static final String GROUP_INSTANCE_ID_CONFIG = "group.instance.id";

    /**
     * A unique identifier of the consumer instance provided by the end user. Only non-empty strings are permitted.
     * If set, the consumer is treated as a static member, which means that only one instance with this ID is allowed in the consumer group at any time.
     * This can be used in combination with a larger session timeout to avoid group rebalances caused by transient unavailability (e.g. process restarts).
     * If not set, the consumer will join the group as a dynamic member, which is the traditional behavior.
     */
    /*
     由终端用户提供的消费者实例唯一标识符（仅允许非空字符串）。
     若设置此标识符，该消费者将被视为静态成员，即消费者组中任意时刻只允许存在一个具有此ID的实例。
     结合较长的会话超时设置使用，可避免因短暂不可用（如进程重启）导致的消费者组再平衡。
     如未设置，消费者将以动态成员身份加入组，这是传统行为模式。
     */
    public static final String GROUP_INSTANCE_ID_DOC = "A unique identifier of the consumer instance provided by the end user. "
                                                       + "Only non-empty strings are permitted. If set, the consumer is treated as a static member, "
                                                       + "which means that only one instance with this ID is allowed in the consumer group at any time. "
                                                       + "This can be used in combination with a larger session timeout to avoid group rebalances caused by transient unavailability "
                                                       + "(e.g. process restarts). If not set, the consumer will join the group as a dynamic member, which is the traditional behavior.";

    // ----------------------------------------------------------------

    public static final String MAX_POLL_INTERVAL_MS_CONFIG = "max.poll.interval.ms";

    /**
     * The maximum delay between invocations of poll() when using consumer group management.
     * This places an upper bound on the amount of time that the consumer can be idle before fetching more records.
     * If poll() is not called before expiration of this timeout,
     * then the consumer is considered failed and the group will rebalance in order to reassign the partitions to another member.
     * For consumers using a non-null <code>group.instance.id</code> which reach this timeout, partitions will not be immediately reassigned.
     * Instead, the consumer will stop sending heartbeats and partitions will be reassigned after expiration of <code>session.timeout.ms</code>.
     * This mirrors the behavior of a static consumer which has shutdown.
     */
    /*
     使用消费者组管理时，两次poll()调用间的最大允许间隔时间。
     此参数设定了消费者在获取更多记录前允许空闲的最长时间上限。
     若在此超时到期前未调用poll()：
     普通消费者会被判定为故障，消费者组将触发再平衡以重新分配分区
     配置了非空<code>group.instance.id</code>的消费者达到此超时后：
     会停止发送心跳
     分区将在<code>session.timeout.ms</code>到期后重新分配
     此行为与静态消费者正常关闭的情况一致
     */
    public static final String MAX_POLL_INTERVAL_MS_DOC = "The maximum delay between invocations of poll() when using "
                                                          + "consumer group management. This places an upper bound on the amount of time that the consumer can be idle "
                                                          + "before fetching more records. If poll() is not called before expiration of this timeout, then the consumer "
                                                          + "is considered failed and the group will rebalance in order to reassign the partitions to another member. "
                                                          + "For consumers using a non-null <code>group.instance.id</code> which reach this timeout, partitions will not be immediately reassigned. "
                                                          + "Instead, the consumer will stop sending heartbeats and partitions will be reassigned "
                                                          + "after expiration of <code>session.timeout.ms</code>. This mirrors the behavior of a static consumer which has shutdown.";

    // ----------------------------------------------------------------

    public static final String REBALANCE_TIMEOUT_MS_CONFIG = "rebalance.timeout.ms";

    /**
     * The maximum allowed time for each worker to join the group once a rebalance has begun.
     * This is basically a limit on the amount of time needed for all tasks to flush any pending data and commit offsets.
     * If the timeout is exceeded, then the worker will be removed from the group, which will cause offset commit failures.
     */
    /*
     再平衡开始后，每个工作节点加入消费者组的最大允许时间。
     该参数本质上是限制所有任务完成待处理数据刷新和偏移量提交所需的时间。
     如果超时，工作节点将从组中移除，这将导致偏移量提交失败。
     */
    public static final String REBALANCE_TIMEOUT_MS_DOC = "The maximum allowed time for each worker to join the group "
                                                          + "once a rebalance has begun. This is basically a limit on the amount of time needed for all tasks to "
                                                          + "flush any pending data and commit offsets. If the timeout is exceeded, then the worker will be removed "
                                                          + "from the group, which will cause offset commit failures.";

    // ----------------------------------------------------------------

    public static final String SESSION_TIMEOUT_MS_CONFIG = "session.timeout.ms";

    /**
     * The timeout used to detect client failures when using Kafka's group management facility.
     * The client sends periodic heartbeats to indicate its liveness to the broker.
     * If no heartbeats are received by the broker before the expiration of this session timeout, then the broker will remove this client from the group and initiate a rebalance.
     * Note that the value must be in the allowable range as configured in the broker configuration by <code>group.min.session.timeout.ms</code> and <code>group.max.session.timeout.ms</code>.
     */
    /*
     使用Kafka组管理功能时，用于检测客户端故障的超时时间。
     客户端会定期向Broker发送心跳以表明其存活状态。
     如果Broker在此会话超时到期前未收到心跳，
     则会将该客户端从组中移除并触发再平衡。
     注意：该值必须在Broker配置<code>group.min.session.timeout.ms</code>和
     <code>group.max.session.timeout.ms</code>规定的允许范围内。
     */
    public static final String SESSION_TIMEOUT_MS_DOC = "The timeout used to detect client failures when using "
                                                        + "Kafka's group management facility. The client sends periodic heartbeats to indicate its liveness "
                                                        + "to the broker. If no heartbeats are received by the broker before the expiration of this session timeout, "
                                                        + "then the broker will remove this client from the group and initiate a rebalance. Note that the value "
                                                        + "must be in the allowable range as configured in the broker configuration by <code>group.min.session.timeout.ms</code> "
                                                        + "and <code>group.max.session.timeout.ms</code>.";

    // ----------------------------------------------------------------

    public static final String HEARTBEAT_INTERVAL_MS_CONFIG = "heartbeat.interval.ms";

    /**
     * The expected time between heartbeats to the consumer coordinator when using Kafka's group management facilities.
     * Heartbeats are used to ensure that the consumer's session stays active and to facilitate rebalancing when new consumers join or leave the group.
     * The value must be set lower than <code>session.timeout.ms</code>, but typically should be set no higher than 1/3 of that value.
     * It can be adjusted even lower to control the expected time for normal rebalances.
     */
    /*
     使用Kafka组管理功能时，预期两次向消费者协调器发送心跳的时间间隔。
     心跳用于：
     确保消费者会话保持活跃
     在新消费者加入或离开时促进再平衡
     该值必须小于<code>session.timeout.ms</code>，
     通常建议不超过其1/3。
     可进一步调低此值以控制常规再平衡的预期时间。
     */
    public static final String HEARTBEAT_INTERVAL_MS_DOC = "The expected time between heartbeats to the consumer "
                                                           + "coordinator when using Kafka's group management facilities. Heartbeats are used to ensure that the "
                                                           + "consumer's session stays active and to facilitate rebalancing when new consumers join or leave the group. "
                                                           + "The value must be set lower than <code>session.timeout.ms</code>, but typically should be set no higher "
                                                           + "than 1/3 of that value. It can be adjusted even lower to control the expected time for normal rebalances.";

    // ----------------------------------------------------------------

    public static final String DEFAULT_API_TIMEOUT_MS_CONFIG = "default.api.timeout.ms";

    /**
     * Specifies the timeout (in milliseconds) for client APIs.
     * This configuration is used as the default timeout for all client operations that do not specify a <code>timeout</code> parameter.
     */
    /*
     指定客户端API的超时时间（毫秒）。
     本配置将作为未指定<code>timeout</code>参数的所有客户端操作的默认超时设置。
     */
    public static final String DEFAULT_API_TIMEOUT_MS_DOC = "Specifies the timeout (in milliseconds) for client APIs. " +
            "This configuration is used as the default timeout for all client operations that do not specify a <code>timeout</code> parameter.";

    // ----------------------------------------------------------------

    /**
     * Postprocess the configuration so that exponential backoff is disabled when reconnect backoff
     * is explicitly configured but the maximum reconnect backoff is not explicitly configured.
     *
     * @param config                    The config object.
     * @param parsedValues              The parsedValues as provided to postProcessParsedConfig.
     *
     * @return                          The new values which have been set as described in postProcessParsedConfig.
     */
    public static Map<String, Object> postProcessReconnectBackoffConfigs(AbstractConfig config,
                                                    Map<String, Object> parsedValues) {
        HashMap<String, Object> rval = new HashMap<>();
        Map<String, Object> originalConfig = config.originals();
        if ((!originalConfig.containsKey(RECONNECT_BACKOFF_MAX_MS_CONFIG)) &&
            originalConfig.containsKey(RECONNECT_BACKOFF_MS_CONFIG)) {
            log.debug("Disabling exponential reconnect backoff because {} is set, but {} is not.",
                    RECONNECT_BACKOFF_MS_CONFIG, RECONNECT_BACKOFF_MAX_MS_CONFIG);
            rval.put(RECONNECT_BACKOFF_MAX_MS_CONFIG, parsedValues.get(RECONNECT_BACKOFF_MS_CONFIG));
        }
        return rval;
    }

    public static void postValidateSaslMechanismConfig(AbstractConfig config) {
        SecurityProtocol securityProtocol = SecurityProtocol.forName(config.getString(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG));
        String clientSaslMechanism = config.getString(SaslConfigs.SASL_MECHANISM);
        if (securityProtocol == SecurityProtocol.SASL_PLAINTEXT || securityProtocol == SecurityProtocol.SASL_SSL) {
            if (clientSaslMechanism == null || clientSaslMechanism.isEmpty()) {
                throw new ConfigException(SaslConfigs.SASL_MECHANISM, null, "When the " + CommonClientConfigs.SECURITY_PROTOCOL_CONFIG +
                        " configuration enables SASL, mechanism must be non-null and non-empty string.");
            }
        }
    }

    public static List<MetricsReporter> metricsReporters(AbstractConfig config) {
        return metricsReporters(Collections.emptyMap(), config);
    }

    public static List<MetricsReporter> metricsReporters(String clientId, AbstractConfig config) {
        return metricsReporters(Collections.singletonMap(CommonClientConfigs.CLIENT_ID_CONFIG, clientId), config);
    }

    public static List<MetricsReporter> metricsReporters(Map<String, Object> clientIdOverride, AbstractConfig config) {
        List<MetricsReporter> reporters = config.getConfiguredInstances(CommonClientConfigs.METRIC_REPORTER_CLASSES_CONFIG,
                MetricsReporter.class, clientIdOverride);
        if (config.getBoolean(CommonClientConfigs.AUTO_INCLUDE_JMX_REPORTER_CONFIG) &&
                reporters.stream().noneMatch(r -> JmxReporter.class.equals(r.getClass()))) {
            JmxReporter jmxReporter = new JmxReporter();
            jmxReporter.configure(config.originals(clientIdOverride));
            reporters.add(jmxReporter);
        }
        return reporters;
    }
}
